#include <Python.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <math.h>
#include <gd.h>


int WHITE[] = {255, 255, 255};

#define PI 3.14159265
#define RADIAN(angle) (PI * (angle) / 360)

#define COLOR(c) (c)[0], (c)[1], (c)[2]

#define MAX_ROTATION 15
#define MAX_K 100

#define YPERIOD 12
#define YAMPLITUDE 10
#define XPERIOD 11
#define XAMPLITUDE 5

//Several predefined colors, used to write text randomly.
#define COLOR_SIZE 3
int FG_COLORS[][COLOR_SIZE] = {
    {27, 78, 181}, //blue
    {22, 163, 35}, //green
    {214, 36, 7},  //red
    /*
    {106, 90, 205},
    {139, 101, 8},
    {255, 20, 147},
    {139, 95, 101},
    {131, 111, 255},
    {0, 0, 255},
    {0, 191, 255},
    {0, 139, 139},
    {139, 139, 132},
    */
};
#define FG_COLORS_CNT (sizeof(FG_COLORS) / sizeof(int[COLOR_SIZE]))

#define IMG_FORMAT "jpeg"


//Randomly generate a integer v such that (min <= v < max).
#define rand_int(min, max) \
    ((int)((min) + ((max) - (min)) * (rand() / (RAND_MAX + 1.0))))


//Randomly generate a integer v such that (min <= v < max) and (v != value).
//Used in write text, gurrantee that two sibling character don't have
//same color.
int rand_int_except(int min, int max, int value){
    int new_value;
    while((new_value = rand_int(min, max)) == value);
    return new_value;
}

int gdImageGaussianBlur(gdImagePtr);


//------------------------- Object Image -------------------------

static PyObject *PyCaptcha_Error;

typedef struct {
    PyObject_HEAD
    gdImagePtr gip;     //gd image object.
    int size;           //finished image data size.
    void *data;          //image memory pointer.
}Captcha;

static void
Captcha_dealloc(Captcha* self){
    if(self->data){
        gdFree(self->data);
    }
    if(self->gip){
        gdImageDestroy(self->gip);
    }
    self->ob_type->tp_free((PyObject*)self);
}

static PyObject *
Captcha_new(PyTypeObject *type, PyObject *args, PyObject *kwds){
    Captcha *self = (Captcha*)(type->tp_alloc(type, 0));
    return (PyObject*)self;
}

static int
Captcha_init(Captcha *self, PyObject *args, PyObject *kwds){
    int width, height;
    static char *kwlist[] = {"width", "height", NULL};
    if(!PyArg_ParseTupleAndKeywords(args, kwds, "ii", kwlist, &width, &height))
        return -1;
    self->gip = gdImageCreate(width, height);
    if(!self->gip){
        PyErr_SetString(PyCaptcha_Error,
                    "Captcha.__init__():gdImageCreate() error!");
        return -1;
    }
    //Set bg color to white
    if(gdImageColorAllocate(self->gip, COLOR(WHITE)) == -1){
        PyErr_SetString(PyCaptcha_Error,
                    "Captcha.__init__():gdImageColorAllocate() error!");
        return -1;
    }
    self->size = -1;
    self->data = NULL;
    return 0;
}


static int
Captcha_wave(gdImagePtr gip){
    int i;
    int xp = XPERIOD * rand_int(1, 4);
    int k = rand_int(0, MAX_K);
    for(i = 0; i < gdImageSX(gip); i++){
        gdImageCopy(gip, gip, i-1, sin((k + ((float)i)/xp)) * XAMPLITUDE,
                    i, 0, 1, gdImageSY(gip));
    }
    k = rand_int(0, MAX_K);
    int yp = YPERIOD * rand_int(1, 3);
    for(i = 0; i < gdImageSY(gip); i++){
        gdImageCopy(gip, gip, sin((k + ((float)i)/yp)) * YAMPLITUDE,
                    i-1, 0, i, gdImageSX(gip), 1);
    }
    return 0;
}


static PyObject *
Captcha_write_text(Captcha *self, PyObject *args, PyObject *kwds){
    int i;
    char *text, *font_file;
    int x = -1, y = -1, len, wave = 1, blur = 1;
    int min_angle = 0 - MAX_ROTATION, max_angle= MAX_ROTATION;
    int spacing = 18, min_size = 20, max_size = 28;

    static char *kwlist[]={"text", "font", "spacing", "min_size", "max_size",
        "x", "y", "min_angle", "max_angle", "wave", "blur", NULL};

    if(!PyArg_ParseTupleAndKeywords(args, kwds, "s#s|iiiiiiiii", kwlist, &text,
                    &len, &font_file, &spacing, &min_size, &max_size, &x, &y,
                    &min_angle, &max_angle, &wave, &blur))
        return NULL;
    if(x < 0 || x > gdImageSX(self->gip))
        x = rand_int(min_size / 2, max_size);
    if(y < 0 || y > gdImageSY(self->gip))
        y = (gdImageSY(self->gip)* 4 / 5.0);
    int angle, fontsize;
    int brect[8];
    char letter[] = " ";

    //foregroud colors, used randomly.
    int fg_colors[FG_COLORS_CNT];
    for(i = 0; i < FG_COLORS_CNT; i++){
        fg_colors[i] = gdImageColorAllocate(self->gip, COLOR(FG_COLORS[i]));
    }

    int color = fg_colors[rand_int(0, FG_COLORS_CNT)];
    for(i = 0; i < len; i++){
        //write the text character by character.
        angle = rand_int(min_angle, max_angle);
        fontsize = rand_int(min_size, max_size + 1);
        letter[0] = text[i];
        char* error = gdImageStringFT(self->gip, brect, color,
                    font_file, fontsize, RADIAN(angle), x, y, letter);
        if(error){
            PyErr_Format(PyCaptcha_Error,
                        "Captcha.write_text():gdImageStringFT() error!%s", error);
            return NULL;
        }
        x += spacing;
    }
    if(wave && Captcha_wave(self->gip) == -1)
        return NULL;
    if(blur && !gdImageGaussianBlur(self->gip)){
        PyErr_SetString(PyCaptcha_Error,
                    "Captcha.write_text():gdImageGaussianBlur() error!");
        return NULL;
    }
    self->data = gdImageJpegPtr(self->gip, &self->size, -1);
    Py_RETURN_NONE;
}


static PyMethodDef Captcha_methods[] = {
    {"write_text", (PyCFunction)Captcha_write_text, METH_VARARGS |
        METH_KEYWORDS,
     "write the given text."
    },
    {NULL}  /* Sentinel */
};


static Py_ssize_t
Captcha_buffer_getreadbuf(Captcha *self, Py_ssize_t segment, const void **ptr)
{
    if(segment != 0){
        PyErr_Format(PyCaptcha_Error,
                    "Captcha.getreadbuf():segment %d not exit!", segment);
        return -1;
    }
    if(self->size == -1){
        PyErr_SetString(PyCaptcha_Error,
                    "Captcha hasn't wirte text yet!");
        return -1;
    }
    *ptr = self->data;
    return self->size;
}


static Py_ssize_t
Captcha_buffer_getsegcount(Captcha *self, Py_ssize_t *lenp)
{
    if (lenp)
        *lenp = self->size;
    return 1;
}


static PyBufferProcs Captcha_as_buffer = {
    (readbufferproc)Captcha_buffer_getreadbuf,
    NULL,
    (segcountproc)Captcha_buffer_getsegcount,
    0, /* END */
};


static PyTypeObject CaptchaType = {
    PyObject_HEAD_INIT(NULL)
    0,                         /*ob_size*/
    "captcha.Captcha",         /*tp_name*/
    sizeof(Captcha),           /*tp_basicsize*/
    0,                         /*tp_itemsize*/
    (destructor)Captcha_dealloc, /*tp_dealloc*/
    0,                         /*tp_print*/
    0,                         /*tp_getattr*/
    0,                         /*tp_setattr*/
    0,                         /*tp_compare*/
    0,                         /*tp_repr*/
    0,                         /*tp_as_number*/
    0,                         /*tp_as_sequence*/
    0,                         /*tp_as_mapping*/
    0,                         /*tp_hash */
    0,                         /*tp_call*/
    0,                         /*tp_str*/
    0,                         /*tp_getattro*/
    0,                         /*tp_setattro*/
    &Captcha_as_buffer,        /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
    "Captcha objects",         /* tp_doc */
    0,		               /* tp_traverse */
    0,		               /* tp_clear */
    0,		               /* tp_richcompare */
    0,		               /* tp_weaklistoffset */
    0,		               /* tp_iter */
    0,		               /* tp_iternext */
    Captcha_methods,           /* tp_methods */
    0,                         /* tp_members */
    0,                         /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)Captcha_init,    /* tp_init */
    0,                         /* tp_alloc */
    Captcha_new,               /* tp_new */
};


static PyMethodDef module_methods[] = {
    {NULL}  /* Sentinel */
};


#ifndef PyMODINIT_FUNC	/* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif


PyMODINIT_FUNC
initpycaptcha(void){
    PyObject* m;
    if (PyType_Ready(&CaptchaType) < 0)
        return;
    m = Py_InitModule3("pycaptcha", module_methods,
                       "Python captcha module.");
    if (m == NULL)
      return;
    Py_INCREF(&CaptchaType);
    PyCaptcha_Error = PyErr_NewException("pycaptcha.PyCaptcha_Error",
                PyExc_Exception, NULL);
    if(PyModule_AddObject(m, "PyCaptcha_Error", PyCaptcha_Error) < 0)
        return;
    if(PyModule_AddObject(m, "Captcha", (PyObject *)&CaptchaType)<0)
        return;
    srand((unsigned)time(NULL));
}
